/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.ai;

import com.serponix.game.objektyNaPlose.VecNaPolicku;
import com.serponix.game.objektyNaPlose.Clanek;
import com.serponix.game.*;
import com.serponix.game.food.Cherry;
import com.serponix.game.food.Cake;
import com.serponix.game.food.Food;
import com.serponix.game.food.TypPotraviny;
import com.serponix.game.objektyNaPlose.Zed;

/**
 * Articifial intelligence of a medium difficulty.
 *
 * @author Daniel Vala
 */
public class AI_STANDARD extends AI {

    private enum Plan {

        JDU_ZA_JIDLEM, JDU_ZA_BONUSEM;
    }
    private final int sirkaPruhuJidla = 3;
    private Direction planovanySmer;
    private boolean odbocDobre;
    private int predchoziVelikostHada;
    int rychlostZmensovani;
    int jedDal;

    /**
     * Přiřadí hráči umělou inteligenci.
     * @param model Model hry.
     * @param hrac Hráč, který je ovládán umělou inteligencí.
     */
    public AI_STANDARD(GameModel model, Player hrac) {
        super(model, hrac);
        predchoziVelikostHada = snake.getDelka();
    }

    /**
     * Ovládá daného hada.
     * Tuto metodu je třeba volat při každém kroku hry.
     */
    public void setNewDirection() {
//        if (jedDal > 0) {
//            jedDal--;
//            hrac.changeSmer(had.getExistujiciSmer());
//            return;
//        }

        checkVelikostHada();

        // jestliže se nevyhýbám žádným hadům, jdu za jídlem
        if (!zkontrolujPredmety()) {
            // pokud jsem moc malý nebo už blízko k vítězství, žeru pouze jídlo
            if (snake.getDelka() < 8 || snake.getDelka() > snake.getVelikostProVitezstvi() - 6) {
                jdiZaPotravinou(TypPotraviny.JIDLO);
            } else {
                jdiZaPotravinou(getBlizsiPotravina());
            }
        }

        predchoziVelikostHada = snake.getDelka();
    }

    private boolean zkontrolujPredmety() {
        if (vyhniSeHadum()) {
            return true;
        } else if (vyhniSeStenam()) {
            return true;
        }
        return false;
    }

    private void zkontrolujStreluNaZadku() {
    }

    private void checkVelikostHada() {
        if (predchoziVelikostHada == snake.getDelka()) {
            if (rychlostZmensovani > 0) {
                rychlostZmensovani--;
            }
        } //pokud jsem se zmenšil
        else if (predchoziVelikostHada > snake.getDelka()) {
            rychlostZmensovani += 10;
        } // pokud jsem se zvětšil
        else if (predchoziVelikostHada < snake.getDelka()) {
        }

        // pokud se zmenšuji příliš rychle, zatočím a tím se vyhnu případným dalším střelám
        if (rychlostZmensovani >= 13) {
            zatoc();
            jedDal = 5;
            rychlostZmensovani = 0;
        }
    }

    /**
     * Pokusí se vyhnout překážce zatočením doleva, či doprava.
     * Rozhodnutí kam zatočí je dáno náhodou.
     * Pokud na stranu, kterou si had vybral, nemůže zatočit, zkusí ještě zatočit na stranu druhou.
     */
    private void vyhniSePrekazce() {
        // nahoda, která rozhodne, zda had zatočí doprava, či doleva
        int nahoda = 1 + randomGenerator.nextInt(2);
        if (nahoda == 1) {
            boolean uspech = changeSmer(Direction.getSmerVlevo(snake.getExistujiciSmer()));
            // pokud se hadovi nepodaří zatočit doleva kvůli jiné překážce, zkusí zatočit doprava
            if (!uspech) {
                changeSmer(Direction.getSmerVpravo(snake.getExistujiciSmer()));
            }
        } else {
            // pokud se hadovi nepodaří zatočit doprava kvůli jiné překážce, zkusí zatočit doleva
            boolean uspech = changeSmer(Direction.getSmerVpravo(snake.getExistujiciSmer()));
            if (!uspech) {
                changeSmer(Direction.getSmerVlevo(snake.getExistujiciSmer()));
            }
        }
    }

    /**
     * Zjistí, zda se v okolí hada nachází nepřátelský had.
     * Pokud ano, snaží se mu různými způsoby vyhnout.
     * @return True, pokud had nějáým způsobem na nepřátelského hada zareagoval nebo se pokusil zareagovat.
     * False, pokud ho ignoruje nebo se v okolí žádný nepřítel nenachází.
     */
    private boolean vyhniSeHadum() {
        VecNaPolicku objektPredHadem = snake.getObjektPredHadem(5);
        if (objektPredHadem != null) {
            if (objektPredHadem instanceof Clanek) {
                Clanek clanek = (Clanek) objektPredHadem;
                Snake nepratelskyHad = clanek.getHad();
                if (nepratelskyHad.isVlastnostNeviditelnost() || nepratelskyHad.isVlastnostDuch()) {
                    return false;
                } else if (!nepratelskyHad.isVlastnostOdolny()) {
                    if (vystrelLepsiZbran()) {
                        return true;
                    }
                    // pokud je had odolný, zkusí had střelit raketu (ne laser)
                } else {
                    if (snake.strelRaketu()) {
                        return true;
                    }
                }
                // pokud jsem tank větší než 8 článků, nevyhnu se protivníkovi
                if (snake.isVlastnostTank() && snake.getDelka() > 8) {
                    return false;
                }
                if (snake.isVlastnostZastaveni()) {
                    changeSmer(Direction.NOWHERE);
                    return true;
                }
                // pokud nemůžu ani vystřelit, ani nejsem tank, ani nemůžu zastavit, hadovy se pokusím vyhnout
                vyhniSePrekazce();
                return true;
            }
        }

        return false;
    }

    public boolean vyhniSeStenam() {
        VecNaPolicku objektPredHadem = snake.getObjektPredHadem(1);
        if (objektPredHadem != null) {
            if (objektPredHadem instanceof Zed) {
                vyhniSePrekazce();
            }
            return true;
        }
        objektPredHadem = snake.getObjektPredHadem(2);
        if (objektPredHadem != null) {
            if (objektPredHadem instanceof Zed) {
                vyhniSePrekazce();
            }
            return true;
        }
        objektPredHadem = snake.getObjektPredHadem(3);
        if (objektPredHadem != null) {
            if (objektPredHadem instanceof Zed) {
                vyhniSePrekazce();
            }
            return true;
        }
        return false;
    }

    /**
     * Vystřelí lepší zbraň.
     * Pokud má raketu, vystřelí ji. Pokud ne, vystřelí laser.
     * @return true, pokud vystřelil. Jinak false.
     */
    private boolean vystrelLepsiZbran() {
        boolean vystrelil = false;
        if (snake.getPocetRaket() > 0) {
            vystrelil = snake.strelRaketu();
            if (!vystrelil) {
                vystrelil = snake.strelLaser();
            }
        } else {
            vystrelil = snake.strelLaser();
        }
        return vystrelil;
    }

    /**
     * Náhodně zatočí na jednu nebo na druhou stranu.
     */
    private void zatoc() {
        int nahoda = 1 + randomGenerator.nextInt(2);
        if (snake.getSmer() == Direction.UP || snake.getSmer() == Direction.DOWN) {
            switch (nahoda) {
                case 1:
                    changeSmer(Direction.LEFT);
                    break;
                case 2:
                    changeSmer(Direction.RIGHT);
                    break;
            }
        } else if (snake.getSmer() == Direction.LEFT || snake.getSmer() == Direction.RIGHT) {
            switch (nahoda) {
                case 1:
                    changeSmer(Direction.UP);
                    break;
                case 2:
                    changeSmer(Direction.DOWN);
                    break;
            }
        }
    }

    /**
     * Had vystřelí s pravděpodobností 1:prpst laser nebo raketu.
     * @return true, pokud vystřelil. Jinak false.
     */
    private boolean nahodnaStrelba(int prpst) {
        int nahoda = 1 + randomGenerator.nextInt(prpst);
        if (nahoda == 1) {
            return snake.strelLaser();
        }
        if (nahoda == 2) {
            return snake.strelRaketu();
        }
        return false;
    }

    /**
     * Vrátí true s pravděpodobností 1:prpst.
     * @param prpst Čím vyšší, tím nepravděpodobněji metoda vrátí true.
     * @return true při štestí, jinak false.
     */
    private boolean nahoda(int prpst) {
        int nahoda = 1 + randomGenerator.nextInt(prpst);
        if (nahoda == 1) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Had se otočí.
     * Zvolí horní zatáčku, pokud je jídlo výš než on.
     * Stejně tak pro všechny ostatní pozice.
     */
    private void otocSe() {
        if (snake.getSmer() == Direction.LEFT) {
            // pokud je jídlo výš než já, otočim se horní zatáčkou
            if (snake.getY() > model.jidlo.getY()) {
                changeSmer(Direction.UP);
                // jinak dolní zatáčkou
            } else {
                changeSmer(Direction.DOWN);
            }
            planovanySmer = Direction.RIGHT;
        } else if (snake.getSmer() == Direction.RIGHT) {
            // pokud je jídlo výš než já, otočim se horní zatáčkou
            if (snake.getY() > model.jidlo.getY()) {
                changeSmer(Direction.UP);
                // jinak dolní zatáčkou
            } else {
                changeSmer(Direction.DOWN);
            }
            planovanySmer = Direction.LEFT;
        } else if (snake.getSmer() == Direction.UP) {
            // pokud je jídlo výš než já, otočim se levou zatáčkou
            if (snake.getX() > model.jidlo.getX()) {
                changeSmer(Direction.LEFT);
                // jinak pravou zatáčkou
            } else {
                changeSmer(Direction.RIGHT);
            }
            planovanySmer = Direction.DOWN;
        } else if (snake.getSmer() == Direction.DOWN) {
            // pokud je jídlo výš než já, otočim se levou zatáčkou
            if (snake.getX() > model.jidlo.getX()) {
                changeSmer(Direction.LEFT);
                // jinak pravou zatáčkou
            } else {
                changeSmer(Direction.RIGHT);
            }
            planovanySmer = Direction.UP;
        }
    }

    /**
     * Had zkusí odbočit s pravděpodobností 1:3 do zadaného směru.
     * @param smer Směr, kam se had má pokusit odbočit.
     */
    private void zkusOdbocit(Direction smer) {
        if (!odbocDobre) {
            int nahoda = 1 + randomGenerator.nextInt(3);
            if (nahoda == 2) {
                changeSmer(smer);
            }
        } else {
            changeSmer(smer);
            odbocDobre = false;
        }
    }

    /**
     * Had zatočí nahoru, pokud je jídlo výš než než on. Jinak zatočí dolu.
     */
    private void korigujVyskuPodlePotraviny(Food food) {
        // pokud je jídlo výš než já, zatočim nahoru
        if (snake.getY() > food.getY()) {
            changeSmer(Direction.UP);
        } // jinak dolu
        else {
            changeSmer(Direction.DOWN);
        }
    }

    /**
     * Had zatočí vlevo, pokud je jídlo nalevo od něj. Jinak zatočí vpravo.
     */
    private void korigujSirkuPodlePotraviny(Food food) {
        // pokud je jídlo výš než já, zatočim vlevo
        if (snake.getX() > food.getX()) {
            changeSmer(Direction.LEFT);
            // jinak vpravo
        } else {
            changeSmer(Direction.RIGHT);
        }
    }

    /**
     * Vrátí typ potraviny, ke které je had blíž.
     * Pokud je ke všem typům potravin stejně daleko, vrátí JIDLO.
     * Pokud bonus neexistuje, vrátí jídlo.
     * @return TypPotraviny, ke které je had nejblíže.
     */
    private TypPotraviny getBlizsiPotravina() {
        Cake jidlo = model.getJidlo();
        Cherry bonus = model.getBonus();

        if (bonus == null) {
            return TypPotraviny.JIDLO;
        }

        int vzdalenostJidlaX = Math.abs(snake.getX() - jidlo.getX());
        int vzdalenostJidlaY = Math.abs(snake.getY() - jidlo.getY());
        int vzdalenostHadaOdJidla = vzdalenostJidlaX + vzdalenostJidlaY;

        int vzdalenostBonusuaX = Math.abs(snake.getX() - bonus.getX());
        int vzdalenostBonusuY = Math.abs(snake.getY() - bonus.getY());
        int vzdalenostHadaOdBonusu = vzdalenostBonusuaX + vzdalenostBonusuY;

        if (vzdalenostHadaOdJidla <= vzdalenostHadaOdBonusu) {
            return TypPotraviny.JIDLO;
        } else {
            return TypPotraviny.BONUS;
        }
    }

    private VecNaPolicku getVecNadHadem() {
        return model.getObjektNaHerniPlose(snake.getX(), snake.getY() - 1);
    }

    private VecNaPolicku getVecPodHadem() {
        return model.getObjektNaHerniPlose(snake.getX(), snake.getY() + 1);
    }

    private VecNaPolicku getVecVlevoOdHada() {
        return model.getObjektNaHerniPlose(snake.getX() - 1, snake.getY());
    }

    private VecNaPolicku getVecVpravoOdHada() {
        return model.getObjektNaHerniPlose(snake.getX() + 1, snake.getY());
    }

    private boolean changeSmer(Direction smer) {
        switch (smer) {
            case UP:
                if (getVecNadHadem() != null) {
                    return false;
                }
                break;
            case DOWN:
                if (getVecPodHadem() != null) {
                    return false;
                }
                break;
            case LEFT:
                if (getVecVlevoOdHada() != null) {
                    return false;
                }
                break;
            case RIGHT:
                if (getVecVpravoOdHada() != null) {
                    return false;
                }
                break;
        }


        // pokud má hráč obrácené klávesy,
        // s pravděpodobností 1:6 si to uvědomí a zatočí správně
//        if (had.isObraceneKlavesy()) {
//            if (!nahoda(6)) {
//                hrac.changeSmer(Smer.obratSmer(smer));
//                return;
//            }
//        }

        player.changeSmer(smer);
        return true;
    }

    /**
     * Had nastaví svůj směr tak, aby co nejdříve sebral danou potravinu.
     * Jako človék se ale nemusí "trefit" a pojede těsně vedle potraviny.
     * Pokud daná potravina na herní ploše není, nic se neprovede.
     */
    private void jdiZaPotravinou(TypPotraviny typPotraviny) {
        if (snake.getSmer() == Direction.NOWHERE) {
            changeSmer(snake.getExistujiciSmer());
            return;
        }

        Food potravina = model.getPotravina(typPotraviny);
        if (potravina == null) {
            return;
        }

        int potravinaX = potravina.getX();
        int potravinaY = potravina.getY();

        // vzdálenosti hada od jídla
        int vzdalenostX = Math.abs(snake.getX() - potravina.getX());
        int vzdalenostY = Math.abs(snake.getY() - potravina.getY());


        if (snake.isVlastnostSvoboda()) {
            int svobodnaVzdalenostX = 0;
            if (snake.getX() > potravinaX) {
                svobodnaVzdalenostX = (model.getSirkaLogicka() - snake.getX()) + potravinaX;
            } else if (snake.getX() < potravinaX) {
                svobodnaVzdalenostX = (model.getSirkaLogicka() - potravinaX) + snake.getX();
            }

            if (svobodnaVzdalenostX < vzdalenostX) {
                potravinaX = model.getSirkaLogicka() + 1;
            }

            int svobodnaVzdalenostY = 0;
            if (snake.getY() > potravinaY) {
                svobodnaVzdalenostY = (model.getVyskaLogicka() - snake.getY()) + potravinaY;
            } else if (snake.getY() < potravinaY) {
                svobodnaVzdalenostY = (model.getVyskaLogicka() - potravinaY) + snake.getY();
            }

            if (svobodnaVzdalenostY < vzdalenostY) {
                potravinaY = model.getVyskaLogicka() + 1;
            }
        }




        // pokud nejsem v žádném pruhu jídla
        if (vzdalenostY >= 3 && vzdalenostX >= 3) {
            // pokud jsem v zóně nalevo nahoře od jídla
            if (snake.getY() < potravinaY && snake.getX() < potravinaX) {
                if (snake.getSmer() == Direction.LEFT) {
                    changeSmer(Direction.DOWN);
                } else if (snake.getSmer() == Direction.UP) {
                    changeSmer(Direction.RIGHT);
                }
            } // pokud jsem v zóně napravo nahoře od jídla
            else if (snake.getY() < potravinaY && snake.getX() > potravinaX) {
                if (snake.getSmer() == Direction.RIGHT) {
                    changeSmer(Direction.DOWN);
                } else if (snake.getSmer() == Direction.UP) {
                    changeSmer(Direction.LEFT);
                }
            } // pokud jsem v zóně nalevo dole od jídla
            else if (snake.getY() > potravinaY && snake.getX() < potravinaX) {
                if (snake.getSmer() == Direction.DOWN) {
                    changeSmer(Direction.RIGHT);
                } else if (snake.getSmer() == Direction.LEFT) {
                    changeSmer(Direction.UP);
                }
            } // pokud jsem v zóně napravo dole od jídla
            else if (snake.getY() > potravinaY && snake.getX() > potravinaX) {
                if (snake.getSmer() == Direction.DOWN) {
                    changeSmer(Direction.LEFT);
                } else if (snake.getSmer() == Direction.RIGHT) {
                    changeSmer(Direction.UP);
                }
            }
        }

        ////////////////  POKUD JSEM V PRUHU JÍDLA ////////////////////


        // jestliže jsem blízko jídla v obou dimenzích, nedělám nic
        if (vzdalenostY < 3 && vzdalenostX < 3) {
            return;
        } // pokud jsem nalevo od jídla
        else if (vzdalenostY < 3 && snake.getX() < potravinaX) {
            if (snake.getSmer() == Direction.DOWN || snake.getSmer() == Direction.UP) {
                zkusOdbocit(Direction.RIGHT);
            } else if (snake.getSmer() == Direction.LEFT) {
                korigujVyskuPodlePotraviny(potravina);
            } // zkorigování směru
            else if (snake.getSmer() == Direction.RIGHT) {
                // pokud had nejede přímo na potravinu
                if (vzdalenostY != 0) {
                    if (nahoda(10)) {
                        korigujVyskuPodlePotraviny(potravina);
                        odbocDobre = true;
                    }
                }
            }
        } // pokud jsem napravo od jídla
        else if (vzdalenostY < 3 && snake.getX() > potravinaX) {
            if (snake.getSmer() == Direction.DOWN || snake.getSmer() == Direction.UP) {
                zkusOdbocit(Direction.LEFT);
            } else if (snake.getSmer() == Direction.RIGHT) {
                korigujVyskuPodlePotraviny(potravina);
            } // zkorigování směru
            else if (snake.getSmer() == Direction.LEFT) {
                // pokud had nejede přímo na potravinu
                if (vzdalenostY != 0) {
                    if (nahoda(10)) {
                        korigujVyskuPodlePotraviny(potravina);
                        odbocDobre = true;
                    }
                }
            }
        } // pokud jsem nahoře nad jídlem
        else if (vzdalenostX < 3 && snake.getY() < potravinaY) {
            if (snake.getSmer() == Direction.RIGHT || snake.getSmer() == Direction.LEFT) {
                zkusOdbocit(Direction.DOWN);
            } else if (snake.getSmer() == Direction.UP) {
                korigujSirkuPodlePotraviny(potravina);
            } // zkorigování směru
            else if (snake.getSmer() == Direction.DOWN) {
                // pokud had nejede přímo na potravinu
                if (vzdalenostX != 0) {
                    if (nahoda(10)) {
                        korigujSirkuPodlePotraviny(potravina);
                        odbocDobre = true;
                    }
                }
            }
        } // pokud jsem dole pod jídlem
        else if (vzdalenostX < 3 && snake.getY() > potravinaY) {
            if (snake.getSmer() == Direction.RIGHT || snake.getSmer() == Direction.LEFT) {
                zkusOdbocit(Direction.UP);
            } else if (snake.getSmer() == Direction.DOWN) {
                korigujSirkuPodlePotraviny(potravina);
            }// zkorigování směru
            else if (snake.getSmer() == Direction.UP) {
                // pokud had nejede přímo na potravinu
                if (vzdalenostX != 0) {
                    if (nahoda(10)) {
                        korigujSirkuPodlePotraviny(potravina);
                        odbocDobre = true;
                    }
                }
            }
        }
    }
}
